//-----------------------------------------------------------------
//
// Copyright (c) 2022-2024 Zhengde
// All rights reserved.
//
//-----------------------------------------------------------------
//                         biRISC-V CPU
//                            V0.8.1
//                     Ultra-Embedded.com
//                     Copyright 2019-2020
//
//                   admin@ultra-embedded.com
//
//                     License: Apache 2.0
//-----------------------------------------------------------------
// Copyright 2020 Ultra-Embedded.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//-----------------------------------------------------------------

module biriscv_divider
(
    // Inputs
    input           clk                 ,
    input           rst_n               ,
    input           opcode_valid_i      ,
    input  [ 31:0]  opcode_opcode_i     ,
    input  [ 31:0]  opcode_pc_i         ,
    input           opcode_invalid_i    ,
    input  [  4:0]  opcode_rd_idx_i     ,
    input  [  4:0]  opcode_ra_idx_i     ,
    input  [  4:0]  opcode_rb_idx_i     ,
    input  [ 31:0]  opcode_ra_operand_i ,
    input  [ 31:0]  opcode_rb_operand_i ,

    // Outputs
    output          writeback_valid_o   ,
    output [ 31:0]  writeback_value_o   
);

    //-----------------------------------------------------------------
    // Includes
    //-----------------------------------------------------------------
    `include "biriscv_defs.v"

    //-------------------------------------------------------------
    // Registers / Wires
    //-------------------------------------------------------------
    reg          valid_q;
    reg  [31:0]  wb_result_q;

    //-------------------------------------------------------------
    // Divider
    //-------------------------------------------------------------
    wire inst_div_w         = (opcode_opcode_i & `INST_DIV_MASK) == `INST_DIV;
    wire inst_divu_w        = (opcode_opcode_i & `INST_DIVU_MASK) == `INST_DIVU;
    wire inst_rem_w         = (opcode_opcode_i & `INST_REM_MASK) == `INST_REM;
    wire inst_remu_w        = (opcode_opcode_i & `INST_REMU_MASK) == `INST_REMU;
    
    wire div_rem_inst_w     = ((opcode_opcode_i & `INST_DIV_MASK) == `INST_DIV)  || 
                              ((opcode_opcode_i & `INST_DIVU_MASK) == `INST_DIVU) ||
                              ((opcode_opcode_i & `INST_REM_MASK) == `INST_REM)  ||
                              ((opcode_opcode_i & `INST_REMU_MASK) == `INST_REMU);
    
    wire signed_operation_w = ((opcode_opcode_i & `INST_DIV_MASK) == `INST_DIV) || ((opcode_opcode_i & `INST_REM_MASK) == `INST_REM);
    wire div_operation_w    = ((opcode_opcode_i & `INST_DIV_MASK) == `INST_DIV) || ((opcode_opcode_i & `INST_DIVU_MASK) == `INST_DIVU);

    reg [31:0] dividend_q;
    reg [62:0] divisor_q;
    reg [31:0] quotient_q;
    reg [31:0] q_mask_q;
    reg        div_inst_q;
    reg        div_busy_q;
    reg        invert_res_q;
    
    reg [31:0] last_a_q;
    reg [31:0] last_b_q;
    reg        last_div_q;
    reg        last_divu_q;
    reg        last_rem_q;
    reg        last_remu_q;

    wire div_start_w    = opcode_valid_i & div_rem_inst_w;
    wire div_complete_w = !(|q_mask_q) & div_busy_q;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
        begin
            div_busy_q     <= 1'b0;
            dividend_q     <= 32'b0;
            divisor_q      <= 63'b0;
            invert_res_q   <= 1'b0;
            quotient_q     <= 32'b0;
            q_mask_q       <= 32'b0;
            div_inst_q     <= 1'b0;
            last_a_q       <= 32'b0;
            last_b_q       <= 32'b0;
            last_div_q     <= 1'b0;
            last_divu_q    <= 1'b0;
            last_rem_q     <= 1'b0;
            last_remu_q    <= 1'b0;
        end
        else if (div_start_w)
        begin
            // Repeat same operation with same inputs...
            if (last_a_q    == opcode_ra_operand_i && 
                last_b_q    == opcode_rb_operand_i &&
                last_div_q  == inst_div_w &&
                last_divu_q == inst_divu_w &&
                last_rem_q  == inst_rem_w &&
                last_remu_q == inst_remu_w)
            begin
                div_busy_q     <= 1'b1;
            end
            else begin
                last_a_q       <= opcode_ra_operand_i;
                last_b_q       <= opcode_rb_operand_i;
                last_div_q     <= inst_div_w;
                last_divu_q    <= inst_divu_w;
                last_rem_q     <= inst_rem_w;
                last_remu_q    <= inst_remu_w;
        
                div_busy_q     <= 1'b1;
                div_inst_q     <= div_operation_w;
        
                if (signed_operation_w && opcode_ra_operand_i[31])
                    dividend_q <= -opcode_ra_operand_i;
                else
                    dividend_q <= opcode_ra_operand_i;
        
                if (signed_operation_w && opcode_rb_operand_i[31])
                    divisor_q <= {-opcode_rb_operand_i, 31'b0};
                else
                    divisor_q <= {opcode_rb_operand_i, 31'b0};
        
                invert_res_q  <= (((opcode_opcode_i & `INST_DIV_MASK) == `INST_DIV) && (opcode_ra_operand_i[31] != opcode_rb_operand_i[31]) && |opcode_rb_operand_i) || 
                                 (((opcode_opcode_i & `INST_REM_MASK) == `INST_REM) && opcode_ra_operand_i[31]);
        
                quotient_q     <= 32'b0;
                q_mask_q       <= 32'h80000000;
            end
        end
        else if (div_complete_w)
        begin
            div_busy_q <= 1'b0;
        end
        else if (div_busy_q)
        begin
            if (divisor_q <= {31'b0, dividend_q})
            begin
                dividend_q <= dividend_q - divisor_q[31:0];
                quotient_q <= quotient_q | q_mask_q;
            end
        
            divisor_q <= {1'b0, divisor_q[62:1]};
            q_mask_q  <= {1'b0, q_mask_q[31:1]};
        end
    end

    reg [31:0] div_result_r;
    always @(*) begin
        div_result_r = 32'b0;
    
        if (div_inst_q)
            div_result_r = invert_res_q ? -quotient_q : quotient_q;
        else
            div_result_r = invert_res_q ? -dividend_q : dividend_q;
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            valid_q <= 1'b0;
        else
            valid_q <= div_complete_w;
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            wb_result_q <= 32'b0;
        else if (div_complete_w)
            wb_result_q <= div_result_r;
    end

    assign writeback_valid_o = valid_q;
    assign writeback_value_o  = wb_result_q;

endmodule
